package com.hzw.saas.common.security.adapter;

import cn.hutool.http.HttpStatus;
import com.hzw.saas.common.security.config.customer.SaasSecurityConfig;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.authentication.TokenExtractor;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

/**
 * 资源服务器默认配置
 *
 * @author zzl
 * @since 12/21/2020
 */
@Slf4j
@Configuration
@EnableResourceServer
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfigAdapter extends ResourceServerConfigurerAdapter {

    private final TokenExtractor tokenExtractor;
    private final SaasSecurityConfig securityConfig;
    private final LogoutSuccessHandler logoutSuccessHandler;
    private final ResourceServerTokenServices resourceServerTokenServices;
    private final AbstractAuthenticationProcessingFilter loginAuthenticationFilter;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        List<String> ignoreAuthUrls = securityConfig.ignoreAuthUrls();
        log.info("ignored urls: " + ignoreAuthUrls);
        http.addFilterBefore(loginAuthenticationFilter, LogoutFilter.class)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            .and()
            .exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
                writeOut(response, accessDeniedException.getMessage(), 401);
            })
            .and()
            .requestMatchers().anyRequest()
            .and()
            .logout().logoutUrl("/user/logout").logoutSuccessHandler(logoutSuccessHandler)
            .and()
            .anonymous()
            .and().headers().frameOptions().disable().and()
            .authorizeRequests()
            .mvcMatchers(ignoreAuthUrls.toArray(new String[0])).permitAll()
            .antMatchers("/**").access("#oauth2.hasScope('all')");
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenExtractor(tokenExtractor);
        resources.resourceId(securityConfig.resourceId()).tokenServices(resourceServerTokenServices);
        resources.authenticationEntryPoint(oAuth2AuthenticationEntryPoint());
        resources.accessDeniedHandler((request, response, ex) -> { // 具有访问资源的用户但无法访问某些资源返回无权限访问
            writeOut(response, ex.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
        });
    }

    /**
     * 自定义异常结果返回
     *
     * @return
     */
    @Bean
    public OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint() {
        return new MyOAuth2AuthenticationEntryPoint();
    }

    @AllArgsConstructor
    public class MyOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws ServletException, IOException {
            if (authException.getCause() instanceof InvalidTokenException) {
                writeOut(response, "无效的凭证: " + authException.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
            } else {
                // 其他异常走默认处理
                super.commence(request, response, authException);
            }
        }
    }

    private void writeOut(HttpServletResponse response, String errorMsg, int status) throws IOException {
        // 抛出的之定义异常单独处理
        response.setContentType("application/json; charset=utf-8"); // 返回 json格式
        response.setStatus(status); // 401
        PrintWriter out = null;
        try {
            out = response.getWriter();
            out.write(errorMsg);
            out.flush();
        } finally {
            out.close();
        }
    }

}
